// Copyright (c) 2008, 
// Marc Speck, http://www.faindu.com
// All Rights Reserved. The following is Source Code and is subject to all restrictions 
// on such code as contained in the license accompanying this product.
package com.faindu.editImage.crop
{
import com.faindu.editImage.EditImage;
import com.faindu.editImage.base.BaseControl;
import com.faindu.editImage.base.BaseTool;
import com.faindu.editImage.base.EditImageEvent;
import com.faindu.editImage.base.IEditImageTools;

import flash.geom.Matrix;
import flash.geom.Point;
import flash.geom.Rectangle;

import mx.core.FlexSprite;

/**
 * Tool that crops the image in parallel to its original sides. By default, 
 * <code>CropTool</code> is disabled.
 */
public class CropTool extends BaseTool implements ICropTool
{
	//==========================================================================
	//
	//  Controls used in this tool
	//
	//--------------------------------------------------------------------------  
	/** @private */
	protected var topLeftControl: BaseControl
	/** @private */
	protected var topControl: BaseControl
	/** @private */
	protected var topRightControl: BaseControl
	/** @private */
	protected var rightControl: BaseControl
	/** @private */
	protected var bottomRightControl: BaseControl
	/** @private */
	protected var bottomControl: BaseControl
	/** @private */
	protected var bottomLeftControl: BaseControl
	/** @private */
	protected var leftControl: BaseControl

	//==========================================================================
	//
	//  init and draw method
    //
	//--------------------------------------------------------------------------  		
	/**
	 * @inheritDoc
	 */
	override public function init( editImage:EditImage, startInteractionHandler:Function ): void
	{
		// initialize properties
		editImageTools = IEditImageTools( editImage )
		editImage.addEventListener( EditImageEvent.CONTROL_DOWN, startControlInteraction )
		
		// create all controls
		topLeftControl        = new CropControl( cropTopLeftInteraction,	TOP_LEFT)
		topControl            = new CropControl( cropTopInteraction, 		TOP )
		topRightControl       = new CropControl( cropTopRightInteraction,	TOP_RIGHT )
		rightControl          = new CropControl( cropRightInteraction,		RIGHT)
		bottomRightControl    = new CropControl( cropBottomRightInteraction,BOTTOM_RIGHT )
		bottomControl         = new CropControl( cropBottomInteraction ,	BOTTOM )
		bottomLeftControl     = new CropControl( cropBottomLeftInteraction,	BOTTOM_LEFT)
		leftControl           = new CropControl( cropLeftInteraction,		LEFT )
				
		// initiate all controlls
		topLeftControl.init( 	editImage, startInteractionHandler, this )
		topControl.init( 		editImage, startInteractionHandler, this )
		topRightControl.init( 	editImage, startInteractionHandler, this )
		rightControl.init( 		editImage, startInteractionHandler, this )
		bottomRightControl.init(editImage, startInteractionHandler, this )
		bottomControl.init(		editImage, startInteractionHandler, this )
		bottomLeftControl.init( editImage, startInteractionHandler, this )
		leftControl.init( 		editImage, startInteractionHandler, this )
	}
	
	/**
	 * @inheritDoc
	 */
	override public function draw(): void
	{
		topLeftControl.draw()
		topControl.draw()
		topRightControl.draw()
		rightControl.draw()
		bottomRightControl.draw()
		bottomControl.draw()
		bottomLeftControl.draw()
		leftControl.draw()
	}
	
	/**
	 * @inheritDoc
	 */
	override public function reset(): void
	{
		topControl.reset()
		topLeftControl.reset()
		topRightControl.reset()
		rightControl.reset()
		bottomRightControl.reset()
		bottomControl.reset()
		bottomLeftControl.reset()
		leftControl.reset()
	}
		
    //==========================================================================
    //
    //  Initialize crop interactions
    //
    //--------------------------------------------------------------------------  
	/**
	 * The location of the mouse in the image coordinate system at the start of the interaction.
	 */
	private var innerInteractionStart: Point
	
	/**
	 * The <code>toolMatrix</code> at the start of the interaction.
	 */
	private var interactionStartMatrix: Matrix

	/**
	 * The inverted <code>toolMatrix</code> at the start of the interaction.
	 */
	private var interactionStartInvertedMatrix: Matrix
	
	/**
	 * The bound of the imageHolder at the start of the interaction.
	 */
	private var interactionStartBound: Rectangle

	/**
	 * Top left corner of the cutout at the start of the interaction.
	 */
	private var interactionStartCrop: Point = new Point( 0, 0 )

	/**
	 * Top left corner of the cutout at the start of the interaction. While
	 * <code>interactionStartCrop</code> is in the coordinate system of <code>spriteImage</code> 
	 * (also called "inner"), <code>interactionStartCropOuter</code> is in the 
	 * coordinate system of <code>EditImage</code>.
	 */
	private var interactionStartCropOuter: Point

	/**
	 * This is width of the image without scaling or cropping.
	 */
	private var originalImageWidth: Number
	
	/**
	 * This is height of the image without scaling or cropping.
	 */
	private var originalImageHeight: Number

	//----------------------------------
	//  minWidth, minHeight
	//----------------------------------		
	/** @private */
	protected var _minWidth: Number = 100
	/**
	 * @inheritDoc
	 */
	public function get minWidth(): Number
	{
		return _minWidth
	}
	public function set minWidth( value:Number ): void
	{
		_minWidth = value
	}

	/** @private */
	protected var _minHeight: Number = 100
	/**
	 * @inheritDoc
	 */
	public function get minHeight(): Number
	{
		return _minHeight
	}
	public function set minHeight( value:Number ): void
	{
		_minHeight = value
	}
	
	/**
	 * @inheritDoc
	 */
	override protected function startControlInteraction( editImageEvent:EditImageEvent ): void
	{
		interactionStartMatrix =  editImageTools.toolMatrix.clone()
 		
 		interactionStartInvertedMatrix = interactionStartMatrix.clone()
		interactionStartInvertedMatrix.invert()
		innerInteractionStart = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		
		var imageHolder: FlexSprite = editImageTools.imageHolder
		interactionStartBound = imageHolder.getBounds( imageHolder )
		if ( imageHolder.scrollRect )
		{
			interactionStartCrop = imageHolder.scrollRect.topLeft
		}
		else
		{
			interactionStartCrop = new Point( 0, 0 )
			imageHolder.scrollRect = interactionStartBound.clone()
		}		
		interactionStartCropOuter = interactionStartMatrix.transformPoint( interactionStartCrop )
		
		originalImageWidth = editImageTools.image.bitmapData.width
		originalImageHeight = editImageTools.image.bitmapData.height 
	}

    //==========================================================================
    //
    //  Crop interactions left, top, right, bottom
    //
    //--------------------------------------------------------------------------  	
	/**
	 * Crop left
	 */		
	protected function cropLeftInteraction( editImageEvent:EditImageEvent ):void
	{
		editImageTools.toolMatrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		var innerDiff:Point = innerMouseLoc.subtract( innerInteractionStart )
		var cutout: Rectangle = editImageTools.imageHolder.scrollRect
		
		// crop left
		var newWidth: Number = interactionStartBound.width - innerDiff.x
		var maxCutoutWidth: Number = interactionStartCrop.x + interactionStartBound.width
		newWidth = ( newWidth > maxCutoutWidth ) ? maxCutoutWidth : newWidth
		newWidth = ( newWidth < minWidth ) ? minWidth: newWidth
		cutout.width = newWidth
		cutout.x = interactionStartCrop.x + interactionStartBound.width - newWidth
		
		// assign mask
		editImageTools.imageHolder.scrollRect = cutout
		
		moveForStandingStill( cutout.topLeft )
	}
	
	/**
	 * @private
	 * Crop top left
	 */		
	protected function cropTopInteraction( editImageEvent:EditImageEvent ): void
	{
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		var innerDiff:Point = innerMouseLoc.subtract( innerInteractionStart )
		var cutout: Rectangle = editImageTools.imageHolder.scrollRect
		
		var newHeight: Number = interactionStartBound.height - innerDiff.y
		var maxCutoutHeight: Number = interactionStartCrop.y + interactionStartBound.height
		newHeight = ( newHeight > maxCutoutHeight ) ? maxCutoutHeight : newHeight
		newHeight = ( newHeight < minHeight ) ? minHeight : newHeight
		cutout.height = newHeight
		cutout.y = interactionStartCrop.y - newHeight + interactionStartBound.height
		
		// assign mask
		editImageTools.imageHolder.scrollRect = cutout
		
 		moveForStandingStill( cutout.topLeft )
	}
	
	/**
	 * @private
	 * Crop right
	 */				
	protected function cropRightInteraction( editImageEvent:EditImageEvent ): void
	{
		editImageTools.toolMatrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		var innerDiff:Point = innerMouseLoc.subtract( innerInteractionStart )
		var cutout: Rectangle = editImageTools.imageHolder.scrollRect
		
		// crop right
		var newWidth: Number = interactionStartBound.width + innerDiff.x
		var maxCutoutWidth: Number = originalImageWidth - interactionStartCrop.x
		newWidth = ( newWidth > maxCutoutWidth ) ? maxCutoutWidth : newWidth  
		newWidth = ( newWidth < minWidth ) ? minWidth : newWidth  
		cutout.width = newWidth
		
		// assign mask
		editImageTools.imageHolder.scrollRect = cutout
	}

	/**
	 * @private
	 * Crop bottom
	 */		
	protected function cropBottomInteraction( editImageEvent:EditImageEvent ): void
	{
		editImageTools.toolMatrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		var innerDiff:Point = innerMouseLoc.subtract( innerInteractionStart )
		var cutout: Rectangle = editImageTools.imageHolder.scrollRect

		// crop bottom
		var newHeight: Number = interactionStartBound.height + innerDiff.y
		var maxCutoutHeight: Number = originalImageHeight - interactionStartCrop.y
		newHeight = ( newHeight > maxCutoutHeight ) ? maxCutoutHeight : newHeight 
		newHeight = ( newHeight < minHeight ) ? minHeight : newHeight 
		cutout.height = newHeight 
		
		// assign mask
		editImageTools.imageHolder.scrollRect = cutout
	}

    //==========================================================================
    //
    //  Crop interactions of corners, e.g. top left corner
    //
    //--------------------------------------------------------------------------  	
	/**
	 * Crop top left
	 */		
	protected function cropTopLeftInteraction( editImageEvent:EditImageEvent ): void
	{
		editImageTools.toolMatrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		var innerDiff:Point = innerMouseLoc.subtract( innerInteractionStart )
		var cutout: Rectangle = editImageTools.imageHolder.scrollRect
		
		// turn it from a left system to a right system
		innerDiff.y *= -1
		innerDiff.x *= -1
		var keepRatio: Boolean = ! editImageEvent.shiftKey
		innerDiff = keepSizeRatio( innerDiff, keepRatio )
		editImageTools.imageHolder.scrollRect = parseCutout( cutout, 
														interactionStartBound, 
														interactionStartCrop.y + interactionStartBound.height,
														interactionStartCrop.x + interactionStartBound.width,
														innerDiff, 
														keepRatio, 
														true,
														true
														) 
		
		moveForStandingStill( cutout.topLeft )
	}
	
	/**
	 * Crop top left
	 */		
	protected function cropTopRightInteraction( editImageEvent:EditImageEvent ): void
	{
		editImageTools.toolMatrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		var innerDiff:Point = innerMouseLoc.subtract( innerInteractionStart )
		var cutout: Rectangle = editImageTools.imageHolder.scrollRect
		
		// turn it from a left system to a right system
		innerDiff.y *= -1
		var keepRatio: Boolean = ! editImageEvent.shiftKey
		innerDiff = keepSizeRatio( innerDiff, keepRatio )
		editImageTools.imageHolder.scrollRect = parseCutout( cutout, 
														interactionStartBound, 
														interactionStartCrop.y + interactionStartBound.height,
														originalImageWidth - interactionStartCrop.x,
														innerDiff, 
														keepRatio, 
														true,
														false
														) 
		
		moveForStandingStill( cutout.topLeft )
	}

	
	/**
	 * Crop top left
	 */		
	protected function cropBottomRightInteraction( editImageEvent:EditImageEvent ): void
	{
		editImageTools.toolMatrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		var innerDiff:Point = innerMouseLoc.subtract( innerInteractionStart )
		var cutout: Rectangle = editImageTools.imageHolder.scrollRect
		
		var keepRatio: Boolean = ! editImageEvent.shiftKey
		innerDiff = keepSizeRatio( innerDiff, keepRatio )
		
		editImageTools.imageHolder.scrollRect = parseCutout( cutout, 
														interactionStartBound, 
														originalImageHeight - interactionStartCrop.y,
														originalImageWidth - interactionStartCrop.x,
														innerDiff, 
														keepRatio, 
														false,
														false
														) 
	}
	
	/**
	 * Crop top left
	 */		
	protected function cropBottomLeftInteraction( editImageEvent:EditImageEvent ): void
	{
		editImageTools.toolMatrix = interactionStartMatrix.clone()
		var innerMouseLoc: Point = interactionStartInvertedMatrix.transformPoint( editImageEvent.mousePosition ) 
		var innerDiff:Point = innerMouseLoc.subtract( innerInteractionStart )
		var cutout: Rectangle = editImageTools.imageHolder.scrollRect

		// turn it from a left system to a right system
		innerDiff.x *= -1
		
		var keepRatio: Boolean = ! editImageEvent.shiftKey
		innerDiff = keepSizeRatio( innerDiff, keepRatio )
		
		editImageTools.imageHolder.scrollRect = parseCutout( cutout, 
												interactionStartBound, 
												originalImageHeight - interactionStartCrop.y,
												interactionStartCrop.x + interactionStartBound.width,
												innerDiff, 
												keepRatio, 
												false,
												true
												) 
		
		moveForStandingStill( cutout.topLeft )
	}
	
    //==========================================================================
    //
    //  Helper functions for crop interactions
    //
    //--------------------------------------------------------------------------  	
	/**
	 * Calculate the new cutout.
	 * 
	 * <p>
	 * <ul>
	 * <li><b>Max Height</b>: Stay within the height of the original image height.
	 * </li>
	 * <li><b>Max Width</b>: Stay within the width of the original image width.
	 * </li>
	 * <li><b>Keep side ratio</b>: If the user does not press the shift key, keep the ratio of height divided by width constant.
	 * </li>
	 * <li><b>Anker the opposite corner or point</b>: If the user crops the top left corner, keep the bottom left corner at the same
	 * place in the display. This means that in some cases the image needs to be moved.
	 * </li>
	 * </ul>
	 * </p> 
	 *  
	 */
	protected function parseCutout( cutout:Rectangle, 
									interactionStartBound:Rectangle, 
									maxCutoutHeight: Number,
									maxCutoutWidth: Number,
									innerDiff:Point, 
									keepRatio:Boolean,
									heightIsMoved:Boolean,
									widthIsMoved:Boolean
									 ): Rectangle
	{
		// crop top
		var newHeight: Number = interactionStartBound.height + innerDiff.y
		var maxHeightReached: Boolean
		if ( newHeight >= maxCutoutHeight )
		{
			newHeight = maxCutoutHeight
			innerDiff.y = newHeight - interactionStartBound.height
			if ( keepRatio ) 
				innerDiff.x = -(interactionStartBound.height-newHeight) / interactionStartBound.height * interactionStartBound.width
		}
		else if ( newHeight < minHeight )
		{
			newHeight = minHeight
			innerDiff.y = newHeight - interactionStartBound.height
			if ( keepRatio )
				innerDiff.x = -(interactionStartBound.height-newHeight) / interactionStartBound.height * interactionStartBound.width
		}
		
		cutout = moveHeight( heightIsMoved, cutout, interactionStartCrop.y, innerDiff.y )

		// crop right 	
  		var newWidth: Number = interactionStartBound.width + innerDiff.x
		
		if ( newWidth > maxCutoutWidth )
		{
			newWidth = maxCutoutWidth
			innerDiff.x = newWidth - interactionStartBound.width
			if ( keepRatio )
			{
 				newHeight = parseNewHeight( newWidth, maxCutoutHeight, interactionStartBound )
 				cutout = moveHeight( heightIsMoved, cutout, interactionStartCrop.y, ( newHeight - interactionStartBound.height ) )
			}
		}
		else if ( newWidth < minWidth )
		{
			newWidth = minWidth
			innerDiff.x = newWidth - interactionStartBound.width
			if ( keepRatio )
			{
 				newHeight = parseNewHeight( newWidth, maxCutoutHeight, interactionStartBound )
 				cutout = moveHeight( heightIsMoved, cutout, interactionStartCrop.y, ( newHeight - interactionStartBound.height ) )
			}
		}
		
		cutout = moveWidth( widthIsMoved, cutout, interactionStartCrop.x, innerDiff.x )
		
		// assign mask
		cutout.height = newHeight
		cutout.width = newWidth
		
		return cutout
	}
	
	/**
	 * Helper class for <code>parseCutout</code>.
	 * 
	 * @param heightIsMoved
	 * @param cutout the reference is modified in the method!
	 * @param interactionStartCrop
	 * @param minus
	 * @return
	 * 
	 * @see #parseCutout() 
	 */
	protected function moveHeight( heightIsMoved:Boolean, cutout:Rectangle, interactionStartCropy:Number, minusTerm:Number ): Rectangle
	{
 		if ( heightIsMoved )
 		{
			var cutouty: Number = interactionStartCropy - minusTerm
			cutout.y =  ( cutouty > 0 ) ? cutouty : 0 
 		}
 		return cutout
	}

	/**
	 * Helper method for <code>parseCutout</code>.
	 * 
	 * @param widthIsMoved
	 * @param cutout
	 * @param interactionStartCropx
	 * @param minusTerm
	 * @return 
	 * 
	 * @see #parseCutout() 
	 */
	protected function moveWidth( widthIsMoved:Boolean, cutout:Rectangle, interactionStartCropx:Number, minusTerm:Number ): Rectangle
	{
 		if ( widthIsMoved )
 		{
			var cutoutx: Number = interactionStartCropx - minusTerm
			cutout.x = ( cutoutx > 0 ) ? cutoutx : 0
 		}
 		return cutout
	}
	
	/**
	 * Helper method for <code>parseCutout</code>.
	 * 
	 * @param newWidth
	 * @param maxCutoutHeigth
	 * @param interactionStartBound
	 * @return
	 * 
	 * @see #parseCutout() 
	 */
	protected function parseNewHeight( newWidth:Number, maxCutoutHeigth:Number, interactionStartBound:Rectangle ): Number
	{
 		var newHeigth: Number = newWidth / interactionStartBound.width * interactionStartBound.height
		if ( newHeigth > maxCutoutHeigth )
			newHeigth = maxCutoutHeigth
		else if ( newHeigth < minHeight )
			newHeigth = minHeight 
			
		return newHeigth
	}
	
	/**
	 * Helper method for <code>parseCutout</code>.
	 * 
	 * @param innerDiff
	 * @param keepRatio
	 * @return the corrected innerDif
	 * 
	 * @see #parseCutout() 
	 */
	protected function keepSizeRatio( innerDiff:Point, keepRatio:Boolean ): Point
	{
		if ( keepRatio ) 
		{
			// have the larger of the movement distances brought down
			// based on the lowest ratio to fit the registration offset
 			var ratioH: Number = interactionStartBound.width ? innerDiff.x / interactionStartBound.width : 0
			var ratioW: Number = interactionStartBound.height ? innerDiff.y / interactionStartBound.height : 0
			
			if ( ratioH > ratioW )
			{
				innerDiff.x = ratioW * interactionStartBound.width
			}
			else
			{
				innerDiff.y = ratioH * interactionStartBound.height
			} 
		}
		return innerDiff
	}
	
	/**
	 * When cropping e.g. the left side, the image needs to be moved in order to 
	 * have a fixed right side. This is because DisplayObject.scrollRect cuts out
	 * the invisible parts of the DisplayObject, in this case the image.
	 */
	protected function moveForStandingStill( cutoutTopLeft:Point ): void
	{
 		var outerTopLeft: Point = interactionStartMatrix.transformPoint( cutoutTopLeft )
 		var curentCropDiff: Point = outerTopLeft.subtract( interactionStartCropOuter )
 		
 		var toolMatrix: Matrix = interactionStartMatrix.clone()
		toolMatrix.tx += curentCropDiff.x
 		toolMatrix.ty += curentCropDiff.y
		editImageTools.toolMatrix = toolMatrix 
	}
}
}